#include "config.h"

#include <time.h>

#define DBG_SUBSYS S_LIBYLIB

#include "dbg.h"
#include "lru.h"


int lru_init(lru_t **lru, int capacity,
             lru_cmp_func cmp_func,
             lru_key_func key_func,
             lru_getkey_func getkey_func,
             lru_load_func load_func,
             lru_unload_func unload_func) {
        int ret;
        lru_t *_lru;

        *lru = NULL;

        hashtable_t ht = hash_create_table(cmp_func, key_func, "lru_cache");
        if (ht == NULL) {
                ret = ENOMEM;
                GOTO(err_ret, ret);
        }

        ret = ymalloc((void **)&_lru, sizeof(lru_t));
        if (unlikely(ret))
                GOTO(err_ret, ret);

        INIT_LIST_HEAD(&_lru->queue);
        _lru->table = ht;
        _lru->capacity = capacity;
        _lru->size = 0;
        _lru->getkey_func = getkey_func;
        _lru->load_func = load_func;
        _lru->unload_func = unload_func;

        *lru = _lru;

        return 0;
err_ret:
        return ret;
}


int lru_destroy(lru_t **lru) {
        lru_t *_lru = *lru;

        if (_lru != NULL) {
                hash_destroy_table(_lru->table, _lru->unload_func);
                _lru->size = 0;

                yfree((void **)lru);
        }

        return 0;
}

int lru_get(lru_t *lru, void *key, void **value) {
        int ret;
        lru_entry_t *entry, *to_be_deleted;

        *value = NULL;

        void *_val = hash_table_find(lru->table, key);
        if (_val == NULL) {
                // add to list head
                lru->load_func(key, &_val);

                if (lru->size == lru->capacity) {
                        // remove list tail
                        entry = (lru_entry_t *)lru->queue.prev;
                        list_del_init(lru->queue.prev);

                        ret = hash_table_remove(lru->table, lru->getkey_func(entry->value),
                                                (void **)&to_be_deleted);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);

                        if (to_be_deleted) {
                                lru->unload_func(to_be_deleted);
                        }

                        lru->size--;
                }

                ret = ymalloc((void **)&entry, sizeof(lru_entry_t));
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                entry->value = _val;
                ret = hash_table_insert(lru->table, (void *)entry, key, 0);
                if (unlikely(ret))
                        GOTO(err_free, ret);

                list_add(&entry->hook, &lru->queue);
                entry->pos = lru->queue.next;

                lru->size++;
        } else {
                entry = (lru_entry_t *)_val;
                if (entry->pos != lru->queue.next) {
                        list_del_init(entry->pos);

                        // make pos pointer to list head
                        list_add(&entry->hook, &lru->queue);
                        entry->pos = lru->queue.next;
                }
        }

        *value = entry->value;
        return 0;
err_free:
        yfree((void **)&entry);
err_ret:
        return ret;
}

int lru_dump(lru_t *lru, int (*handler)(void *arg, void *entry)) {
        struct list_head *pos;

        printf("== size %d\n", lru->size);

        printf("== queue\n");
        list_for_each(pos, &lru->queue) {
                handler(NULL, pos);
        }

        printf("== hash\n");

        hash_iterate_table_entries(lru->table, handler, NULL);

        return 0;
}
